<?php

	/*
	*class for fetching or any movings with the query result
	*/
	class FetchDb {
		protected $query_resource;
		protected $error = array();

		//===================================================================================

		public function __construct($query_resourse, $connect_id, $error_params = array()) {
			global $G;

			$this->query_resource = $query_resourse;

			if (!empty($error_params))
			{
				$this->error = array(
					'query' => $error_params['query'],
					'error' => mysql_error($connect_id),
					'errno' => mysql_errno($connect_id),
				);

				//mail

				if (Mysql::isSendErrorEmail())
				{
					$subject_email = 'Other';
					switch(intval($this->error['errno']))
					{
						//syntax error
						case 1064:
							$subject_email = 'Syntax';
						break;
						case 1062:
							$subject_email = 'Duplicate key';
						break;
						case 1290:
							$subject_email = 'Read-only option';
						break;
						case 1146:
							$subject_email = 'Table is absent';
						break;
						case 2006:
							$subject_email = 'Mysql server has gone away';
						break;
						case 0:
							$subject_email = 'Connection';
						break;
					}
					$subject_email = ('dev' === $G["inst_def"]->inst_type ? 'DEV ' : '').'DB Error ('.$subject_email.') on '.date('Y-m-d').' ('.$G["inst_def"]->inst_type.('dev' === $G["inst_def"]->inst_type ? ':'.$G['inst_def']->port : '').')';
					//sent debug mail
					$message = 'DB error: '.print_r($this->error, 1)."\n".
								"Backtrace: \n".getBackTrace(2)."\n".
								"HOST: ".$G['hostname']."\n".
								"Db key: ".$error_params['parent']->key."\n".
								"Time after connect: ".(microtime(1) - $error_params['parent']->start_driver_time)."\n\n-------------------------------------------\n\n".
								Mysql::createTimeRuns().
								"\n".print_r($_SERVER, 1)."\n";

					echo "<br/><br/><pre>".print_r($message, 1)."</pre><br/><br/>";
					//Mailer($G["sql-errors_email"], $subject_email, $message, 'text/plain', 'debug@huffingtonpost.com');
				}
			}
		}

		//===================================================================================

		public function isError() {
			return !empty($this->error);
		}

		//===================================================================================

		public function getError() {
			$error = $this->error;
			$error['error'] = 0 === intval($this->error['errno']) ? 'DB connection is absent' : $this->error['error'];
			return $error;
		}

		//===================================================================================

		public function getTextError() {
			if (empty($this->error)) return 'No error';
			$error = $this->error;
			$error['error'] = 0 === intval($this->error['errno']) ? 'DB connection is absent' : $this->error['error'];
			return 'DB error ('.$error['errno'].'): '.$error['error'];
		}

		//===================================================================================

		public function getRow() {
			if ($this->isError()) return $this;

			if ($row = mysql_fetch_array($this->query_resource, MYSQL_ASSOC))
			{
				return $row;
			}

			return array();
		}

		//===================================================================================

		public function fetchRow() {
			//alias to getRow
			return $this->getRow();
		}

		//===================================================================================

		public function getOne() {
			if ($this->isError()) return $this;

			if ($row = $this->getRow())
			{
				return array_shift($row);
			}

			return null;
		}

		//===================================================================================

		public function numRows() {
			if ($this->isError()) return $this;
			return mysql_num_rows($this->query_resource);
		}

		//===================================================================================

		public function getCol() {
			if ($this->isError()) return $this;

			$ret = array();
			while ($_ret = $this->getRow())
			{
				$ret[] = array_shift($_ret);
			}
			unset($_ret);
			return $ret;
		}

		//===================================================================================

		public function getAll() {
			if ($this->isError()) return $this;

			$ret = array();
			while ($_ret = $this->getRow())
			{
				$ret[] = $_ret;
			}
			unset($_ret);
			return $ret;
		}

		//===================================================================================

		public function getAllByIds($field = 'id') {
			if ($this->isError()) return $this;

			$ret = array();
			while ($_ret = $this->getRow())
			{
				if (isset($_ret[$field]))
				{
					$ret[$_ret[$field]] = $_ret;
				}
				else
				{
					$ret[] = $_ret;
				}
			}
			unset($_ret);
			return $ret;
		}

		//===========================================================================
	}

	/*
	*
	* singleton
	*
	*/

	class Mysql {
		private static $db_handles = array();

		public static $time_labels = array();
		public static $start_time = 0;
		public static $memory_save = false;

		//===================================================================================

		public function __construct() {

		}

		//===================================================================================

		static public function getInstance($key = 'db') {
			if (!self::$memory_save) {
				Mysql::setTimeLabel('Request for db instance', array('time' => 0, 'db_key' => $key));
			}

			if (isset(self::$db_handles[$key])) return self::$db_handles[$key];
			if (empty(self::$start_time)) self::$start_time = microtime(1);

			return self::$db_handles[$key] = new MysqlDriver($key);
		}

		//===================================================================================

		public static function setTimeLabel($query, $params = array())
		{
			$params['timemark'] = microtime(1);
			$params['query'] = $query;
			self::$time_labels[] = $params;
		}

		//===================================================================================

		public static function createTimeRuns($full = 1) {
			if (empty(self::$time_labels)) return '';

			$stats = array();

			$time = self::$start_time;
			$ret = '';
			foreach (self::$time_labels as $params) {
				if ($params['time'] > 0) {
					//collect query stats
					if (!isset($stats[$params['db_key']])) {
						$stats[$params['db_key']] = array('queries' => 0, 'total_time' => 0);
					}
					$stats[$params['db_key']]['queries']++;
					$stats[$params['db_key']]['total_time'] += $params['time'];
				}
				if ($full) {
					$ret .= "\n-------------------------------------------------------\n\n[query] => ".$params['query']."\n[key] => ".$params['db_key']."\n[execution time] => ".$params['time']."\n[timemark] => ".($params['timemark'] - $time)."\n";
				}
			}

			if (!empty($stats))
			{
				$tmp = '';
				foreach ($stats as $key => $params) {
					$tmp .= "\n=========================================================\n\n[key] => ".$key."\n[queries] => ".$params['queries']."\n[total execution time] => ".$params['total_time']."\n\n";
				}
				$ret = $tmp."\n=========================================================\n\n".$ret;
			}

			return $ret;
		}

		//===========================================================================

		public static function setmemory_saveMode() {
			self::$memory_save = true;
		}

		//===========================================================================

		public static function setMemoryNormalMode() {
			self::$memory_save = false;
		}

		//===================================================================================

		public function isSendErrorEmail() {

			global $G;

			if (empty($G['mysql_debug_disabled'])) {
				if (!empty($G["sql-errors_email"])) {
					return true;
				}
			}

			return false;
		}
	}

	/*
	*
	* main core Db class
	*
	*/

	class MysqlDriver extends Mysql
	{
		protected $connect_id = 0;
		protected $db_params = array();
		protected $query_resource;
		protected $reconnects = 0;
		protected $max_reconnects = 1;

		public $start_driver_time = 0;
		public $key = '';
		public $last_sql = '';

		private static $config_path = 'etc/db.json';

		//===================================================================================

		function __construct($key = 'db') {
			parent::__construct();

			if (empty($this->db_params)) {
				$config = json_decode(file_get_contents(TOP_DIR.'/'.self::$config_path), 1);
				if (!empty($config) && isset($config[$key]))
				{
					$this->db_params = $config[$key];
				}
			}

			$this->key = $key;

			if (empty($this->db_params)) {
				 echo 'no db params found';
				 return false;
			}
		}

		//===================================================================================

		public function query($query, $params = array())
		{
			global $G;

			if (!$this->connect_id) {
				$this->connect();
				Mysql::setTimeLabel('REAL CONNECT', array('time' => 0, 'db_key' => $this->key));
			}

			if (!empty($params))
			{
				$_sql = '';
				$query_parts = explode('?', $query);

				foreach ($params as $ind => $param) {
					//escaping
					if ($param !== 'NOW()')
						$param = $this->quote($param);
					$_sql .= $query_parts[$ind].$param;
					unset( $query_parts[$ind]);
				}
				if (!empty($query_parts))
				{
					foreach ($query_parts as $part)
						$_sql .= $part;
				}
				$query = $_sql;
				unset($_sql);
			}

			//strip out leading tabs and ending ones
			$query = trim($query);
			$this->last_sql = $query;
			$start = microtime(1);
			$this->query_resource = mysql_query($query, $this->connect_id);
			$end = microtime(1);
			if (!Mysql::$memory_save) {
				Mysql::setTimeLabel($query, array('time' => $end - $start, 'db_key' => $this->key));
			}

			if ($G["inst_def"]->inst_type == 'prod' && $end - $start > 10) {
				//sent debug mail
				$message = "Long query (".($end - $start)."): ".$query."\n";
				$message .= "HOST: ".$G['hostname']."\n Slave status:";
				$tmp = mysql_query('SHOW SLAVE STATUS', $this->connect_id);
				$fdb = new FetchDb($tmp, $this->connect_id);
				$message .= print_r($fdb->getAll(), 1);
				$tmp = mysql_query('SHOW FULL PROCESSLIST', $this->connect_id);
				$fdb = new FetchDb($tmp, $this->connect_id);
				$list = $fdb->getAll();
				foreach ($list as $index => $data) {
					if ($data['Command'] == 'Sleep') unset($list[$index]);
				}
				$message .= print_r($list, 1)."\n".print_r($_SERVER, 1)."\n";
				$subject = 'Long query ('.$G["inst_def"]->inst_type.')';
				if (strpos($query, 'hp_twitter_') !== false)
				{
					$subject = 'Long TWITTER query ('.$G["inst_def"]->inst_type.')';
				}
				echo "<br/><br/><pre>".print_r($message, 1)."</pre><br/><br/>";
				//Mailer($G["sql-errors_email"], $subject, $message, 'text/plain');
			}

			if (!$this->query_resource) {
				$error_no = mysql_errno($this->connect_id);
				if ($error_no == 2006 && $this->reconnects < $this->max_reconnects) {
					$this->reconnects++;

					if ($this->isSendErrorEmail())
					{
						//mail just for history
						$message = "RECONNECT DETECTED\n\n".
									"HOST: ".$G['hostname']."\n".
									"Backtrace: \n".getBackTrace()."\n".
									"Db key: ".$this->key."\n".
									"Time after connect: ".(microtime(1) - $this->start_driver_time)."\n\n-------------------------------------------\n\n".
									(!empty(Plugin::$time_labels) ? "Plugin timeruns: ".Plugin::createTimeRuns() : '')."\n".
									Mysql::createTimeRuns().
									"\n\n-------------------------------------------\n\n".
									"\n THE PREVIOUS QUERY FAILED WITH 'Mysql has gone away' error \n".
									"\n\n-------------------------------------------\n\n".
									"\n".print_r($_SERVER, 1)."\n";
						echo "<br/><br/><pre>".print_r($message, 1)."</pre><br/><br/>";
						//Mailer($G["sql-errors_email"], 'DB reconnect '.$this->reconnects.' ('.$G["inst_def"]->inst_type.') on '.date('Y-m-d'), $message, 'text/plain', 'debug@huffingtonpost.com');
					}

					//if timeout error then try to reconnect
					$this->connect(1);

					//re-run the query, don't pass $params because they are processed
					return $this->query($query);
				}
				else
				{
					//don't want to reconnect, throw an error
					return new FetchDb($this->query_resource, $this->connect_id, array('query' => $query, 'parent' => $this));
				}
			}

			return new FetchDb($this->query_resource, $this->connect_id);
		}

		//===================================================================================

		private function connect($reconnect = false) {
			if (!$this->connect_id = mysql_connect($this->db_params['host'].':'.$this->db_params['port'], $this->db_params['user'], $this->db_params['password'], 1))
			{
				echo "<!-- ".mysql_error()." -->";
				Mysql::setTimeLabel('COULD NOT CONNECT TO DB, '.mysql_error(), array('time' => 0, 'db_key' => $this->key));
				return false;
			}

			if ($reconnect) {
				Mysql::setTimeLabel('RECONNECT '.$this->reconnects, array('time' => 0, 'db_key' => $this->key));
			}

			if (!$this->start_driver_time) {
				$this->start_driver_time = microtime(1);
			}

			if (!mysql_select_db($this->db_params['dbname'], $this->connect_id))
			{
				echo "<!-- ".mysql_error()." -->";
				Mysql::setTimeLabel('COULD NOT SELECT DB '.$this->db_params['dbname'].', '.mysql_error(), array('time' => 0, 'db_key' => $this->key));
				return false;
			}

			return true;
		}

		//===================================================================================

		function escape($param, $is_like = false) {
			if (!$this->connect_id) {
				$this->connect();
			}

			if ($is_like)
				$param = str_replace('\\','\\\\',$param);
			$param = mysql_real_escape_string($param, $this->connect_id);
			if ($is_like)
				$param = addCslashes($param, '_%');

			return $param;
		}

		//===================================================================================

		function quote($param, $is_like = false) {
			if ($param === NULL) {
				$param = " '' ";
			}
			elseif (gettype($param) == 'string' || empty($param))
			{
				$param = " '".$this->escape($param, $is_like)."' ";
			}

			return $param;
		}

		//===================================================================================

		function getDetails($table_name, $id, $primary_field = 'id') {
			$query = 'SELECT * FROM `'.$table_name.'` WHERE '.$primary_field.' = ? LIMIT 1';
			return $this->query($query, array($id))->getRow();
		}

		//===================================================================================

		function getAll($query, $params = array())
		{
			return $this->query($query, $params)->getAll();
		}

		//===================================================================================

		function getAllByIds($query, $field = 'id', $params = array())
		{
			return $this->query($query, $params)->getAllByIds($field);
		}

		//===================================================================================

		function getRow($query, $params = array())
		{
			return $this->query($query, $params)->getRow();
		}

		//===================================================================================

		function getOne($query, $params = array())
		{
			return $this->query($query, $params)->getOne();
		}

		//===================================================================================

		function getCol($query, $params = array())
		{
			return $this->query($query, $params)->getCol();
		}

		//===================================================================================

		function update($table_name, $values, $id, $primary_field = 'id') {
			if (empty($values) || empty($id)) return;

			if (isset($values[$primary_field])) unset($values[$primary_field]);

			$query = 'UPDATE `'.$table_name.'` SET ';
			$fields = array();
			foreach ($values as $name => $value) {
				$fields[] = '`'.$name.'` = ?';
			}

			$update_where = ' `'.$primary_field.'` = ?';
			$values[] = $id;

			if (is_array($id))
			{
				$update_where = ' `'.$primary_field.'` IN ("'.implode('","', $id).'")';
				array_pop($values);
			}

			$query .= implode(', ', $fields).' WHERE '.$update_where;

			return $this->query($query, array_values($values));
		}

		//===================================================================================

		function insert($table_name, $values) {
			if (empty($values)) return;

			$query = 'INSERT INTO `'.$table_name.'` (';
			$fields = $insert_values = array();
			foreach ($values as $name => $value) {
				$fields[] = '`'.$name.'`';
				$insert_values[] = '?';

			}
			$query .= implode(', ', $fields).') VALUES(';
			$query .= implode(', ', $insert_values).')';

			return $this->query($query, array_values($values));
		}

		//===================================================================================

		public function delete($table_name, $ids, $primary_field = 'id') {
			if (empty($ids)) return;

			if (!is_array($ids)) $ids = array($ids);

			$query = 'DELETE FROM `'.$table_name.'` WHERE `'.$primary_field.'` IN ("'.implode('","', $ids).'")';
			return $this->query($query);
		}

		//===========================================================================

		public function isError($res) {
			if ($res instanceof FetchDb) {
				return $res->isError();
			}

			return false;
		}

		//===========================================================================

		public function getError($res) {
			return $res->getError();
		}

		//===========================================================================

		public function lastInsertId() {
			return mysql_insert_id($this->connect_id);
		}

		//===========================================================================
	}





?>
